
// Copyright (c) 2010-2021 niXman (github dot nixman at pm dot me). All
// rights reserved.
//
// This file is part of YAS(https://github.com/niXman/yas) project.
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//
//
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef __yas__object_hpp
#define __yas__object_hpp

#include <yas/detail/preprocessor/preprocessor.hpp>
#include <yas/detail/type_traits/type_traits.hpp>
#include <yas/detail/tools/fnv1a.hpp>
#include <yas/detail/tools/ctsort.hpp>
#include <yas/detail/tools/ctmap.hpp>

#include <cstring>

namespace yas {

/***************************************************************************/

template<typename T>
struct value {
    template<typename VT>
    struct real_value_type {
        using type = typename std::conditional<
             std::is_array<typename std::remove_reference<VT>::type>::value
            ,typename std::remove_cv<VT>::type
            ,typename std::conditional<
                 std::is_lvalue_reference<VT>::value
                ,VT
                ,typename std::decay<VT>::type
            >::type
        >::type;
    };
    using key_type   = const char*;
    using value_type = typename real_value_type<T>::type;

    value(const value &) = delete;
    value& operator=(const value &) = delete;

    constexpr value(const char *k, std::size_t klen, T &&v) noexcept
        :key(k)
        ,klen(klen)
        ,val(std::forward<T>(v))
    {}
    constexpr value(value &&r) noexcept
        :key(r.key)
        ,klen(r.klen)
        ,val(std::forward<value_type>(r.val))
    {}

    key_type key;
    const std::size_t klen;
    value_type val;
};

template<std::size_t N, typename T>
constexpr value<T>
make_val(const char (&key)[N], T &&val) {
    return {key, N-1, std::forward<T>(val)};
}

template<typename ConstCharPtr, typename T>
constexpr typename std::enable_if<
     std::is_same<ConstCharPtr, const char*>::value
    ,value<T>
>::type
make_val(ConstCharPtr key, T &&val) {
    return {key, std::strlen(key), std::forward<T>(val)};
}

/***************************************************************************/

template<typename KVI, typename... Pairs>
struct object {
    using tuple = std::tuple<Pairs...>;

    object(const object &) = delete;
    object& operator=(const object &) = delete;

    constexpr object(const char *k, std::size_t klen, Pairs&&... pairs) noexcept
        :key(k)
        ,klen(klen)
        ,pairs(std::forward<Pairs>(pairs)...)
    {}
    constexpr object(object &&r) noexcept
        :key(r.key)
        ,klen(r.klen)
        ,pairs(std::move(r.pairs))
    {}

    const char *key;
    const std::size_t klen;
    tuple pairs;

    static constexpr detail::ctmap<KVI> map{};
};
template<typename KVI, typename... Pairs>
constexpr detail::ctmap<KVI> object<KVI, Pairs...>::map;

template<typename KVI, std::size_t N, typename... Pairs>
constexpr object<KVI, Pairs...>
make_object(const char (&key)[N], Pairs &&... pairs) {
    return {key, N-1, std::forward<Pairs>(pairs)...};
}

template<typename KVI, typename... Pairs>
constexpr object<KVI, Pairs...>
make_object(std::nullptr_t, Pairs &&... pairs) {
    return {nullptr, 0, std::forward<Pairs>(pairs)...};
}

/**************************************************************************/

#if defined(YAS_SERIALIZE_BOOST_TYPES)
#   include <boost/version.hpp>
#   if BOOST_VERSION >= 106000
#       define __YAS_CAN_USE_BOOST_VMD
#   endif // BOOST_VERSION >= 106000
#endif // defined(YAS_SERIALIZE_BOOST_TYPES)

#ifdef __YAS_CAN_USE_BOOST_VMD
#   include <boost/vmd/is_empty.hpp>
#   define __YAS_PP_TUPLE_IS_EMPTY(...) BOOST_VMD_IS_EMPTY(__VA_ARGS__)
#else
#   define __YAS_PP_ARG50( \
          _0 , _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 \
        , _10, _11, _12, _13, _14, _15, _16, _17, _18, _19 \
        , _20, _21, _22, _23, _24, _25, _26, _27, _28, _29 \
        , _30, _31, _32, _33, _34, _35, _36, _37, _38, _39 \
        , _40, _41, _42, _43, _44, _45, _46, _47, _48, _49 \
        , ...) _49
#   define __YAS_PP_HAS_COMMA(...) \
        __YAS_PP_ARG50( \
            __VA_ARGS__, \
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
            1, 1, 1, 1, 1, 1, 1, 1, 0 \
        )
#   define __YAS_PP_TRIGGER_PARENTHESIS_(...) ,
#   define __YAS_PP_PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#   define __YAS_PP_IS_EMPTY_CASE_0001 ,
#   define __YAS_PP_IS_EMPTY(_0, _1, _2, _3) __YAS_PP_HAS_COMMA(__YAS_PP_PASTE5(__YAS_PP_IS_EMPTY_CASE_, _0, _1, _2, _3))
#   define __YAS_PP_TUPLE_IS_EMPTY_IMPL(...) \
        __YAS_PP_IS_EMPTY( \
            __YAS_PP_HAS_COMMA(__VA_ARGS__), \
            __YAS_PP_HAS_COMMA(__YAS_PP_TRIGGER_PARENTHESIS_ __VA_ARGS__),                 \
            __YAS_PP_HAS_COMMA(__VA_ARGS__ (/*empty*/)), \
            __YAS_PP_HAS_COMMA(__YAS_PP_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/)) \
        )
#   define __YAS_PP_TUPLE_IS_EMPTY(...) __YAS_PP_TUPLE_IS_EMPTY_IMPL(__VA_ARGS__)
#endif // __YAS_CAN_USE_BOOST_VMD

/**************************************************************************/

#define __YAS_OBJECT_GEN_PAIRS_IMPL(forstruct, sname, idx, elem) \
    YAS_PP_COMMA_IF(idx) \
        ::yas::make_val( \
             YAS_PP_STRINGIZE(elem) \
            ,YAS_PP_IF(forstruct, sname., /*empty*/) elem \
        )

#define __YAS_OBJECT_GEN_PAIRS(unused0, data, idx, elem) \
    __YAS_OBJECT_GEN_PAIRS_IMPL( \
         YAS_PP_TUPLE_ELEM(0, data) \
        ,YAS_PP_TUPLE_ELEM(1, data) \
        ,idx \
        ,elem \
    )

#define __YAS_OBJECT_IMPL(forstruct, sname, seq) \
    YAS_PP_SEQ_FOR_EACH_I( \
         __YAS_OBJECT_GEN_PAIRS \
        ,(forstruct, sname) \
        ,seq \
    )

#define __YAS_OBJECT_NONEMPTY_GEN_KVI_CB(unised0, unised1, idx, elem) \
    YAS_PP_COMMA_IF(idx) \
    std::pair< \
        std::integral_constant< \
             std::uint32_t \
            ,::yas::detail::fnv1a(YAS_PP_STRINGIZE(elem)) \
        > \
        ,std::integral_constant<std::uint8_t, idx> \
    >

#define __YAS_OBJECT_NONEMPTY_GEN_KVI(seq) \
    typename ::yas::detail::mergesort< \
        ::yas::detail::predic_less \
        ,std::tuple< \
            YAS_PP_SEQ_FOR_EACH_I( \
                 __YAS_OBJECT_NONEMPTY_GEN_KVI_CB \
                ,~ \
                ,seq \
            ) \
        > \
    >::type

#define __YAS_OBJECT_EMPTY(forstruct, oname, sname, ...) \
    ::yas::make_object<std::tuple<>>(oname)

#define __YAS_OBJECT_NONEMPTY(forstruct, oname, sname, seq) \
    ::yas::make_object< \
        __YAS_OBJECT_NONEMPTY_GEN_KVI(seq) \
    >( \
         oname \
        ,__YAS_OBJECT_IMPL(forstruct, sname, seq) \
    )

#define __YAS_OBJECT_AUX(forstruct, oname, sname, ...) \
    YAS_PP_IF( \
         __YAS_PP_TUPLE_IS_EMPTY(__VA_ARGS__) \
        ,__YAS_OBJECT_EMPTY \
        ,__YAS_OBJECT_NONEMPTY \
    )(forstruct, oname, sname, YAS_PP_TUPLE_TO_SEQ((__VA_ARGS__)))

#define YAS_OBJECT(oname, ...) \
    __YAS_OBJECT_AUX( \
         0 \
        ,oname \
        ,~ \
        ,__VA_ARGS__ \
    )

#define YAS_OBJECT_STRUCT(oname, sname, ...) \
    __YAS_OBJECT_AUX( \
         1 \
        ,oname \
        ,sname \
        ,__VA_ARGS__ \
    )

/***************************************************************************/

#define __YAS_OBJECT_NVP_GEN_PAIRS_IMPL(forstruct, sname, idx, elem) \
    YAS_PP_COMMA_IF(idx) \
    ::yas::make_val( \
         YAS_PP_TUPLE_ELEM(0, elem) \
        ,YAS_PP_IF(forstruct, sname., /*empty*/) YAS_PP_TUPLE_ELEM(1, elem) \
    )

#define __YAS_OBJECT_NVP_GEN_PAIRS(unused0, data, idx, elem) \
    __YAS_OBJECT_NVP_GEN_PAIRS_IMPL( \
         YAS_PP_TUPLE_ELEM(0, data) \
        ,YAS_PP_TUPLE_ELEM(1, data) \
        ,idx \
        ,elem \
    )

#define __YAS_OBJECT_NVP_IMPL(forstruct, sname, seq) \
    YAS_PP_SEQ_FOR_EACH_I( \
         __YAS_OBJECT_NVP_GEN_PAIRS \
        ,(forstruct, sname) \
        ,seq \
    )

#define __YAS_OBJECT_NVP_NONEMPTY_GEN_KVI_CB(unised0, unised1, idx, elem) \
    YAS_PP_COMMA_IF(idx) \
    std::pair< \
         std::integral_constant< \
              std::uint32_t \
             ,::yas::detail::fnv1a(YAS_PP_TUPLE_ELEM(0, elem)) \
         > \
        ,std::integral_constant<std::uint8_t, idx> \
    >

#define __YAS_OBJECT_NVP_NONEMPTY_GEN_KVI(seq) \
    typename ::yas::detail::mergesort< \
        ::yas::detail::predic_less \
        ,std::tuple< \
            YAS_PP_SEQ_FOR_EACH_I( \
                 __YAS_OBJECT_NVP_NONEMPTY_GEN_KVI_CB \
                ,~ \
                ,seq \
            ) \
        > \
    >::type

#define __YAS_OBJECT_NVP_EMPTY(forstruct, oname, sname, ...) \
    ::yas::make_object<std::tuple<>>(oname)

#define __YAS_OBJECT_NVP_NONEMPTY(forstruct, oname, sname, seq) \
    ::yas::make_object< \
        __YAS_OBJECT_NVP_NONEMPTY_GEN_KVI(seq) \
    >( \
         oname \
        ,__YAS_OBJECT_NVP_IMPL(forstruct, sname, seq) \
    )

#define __YAS_OBJECT_NVP_AUX(forstruct, oname, sname, ...) \
    YAS_PP_IF( \
         __YAS_PP_TUPLE_IS_EMPTY(__VA_ARGS__) \
        ,__YAS_OBJECT_NVP_EMPTY \
        ,__YAS_OBJECT_NVP_NONEMPTY \
    )(forstruct, oname, sname, YAS_PP_TUPLE_TO_SEQ((__VA_ARGS__)))


#define YAS_OBJECT_NVP(oname, ...) \
    __YAS_OBJECT_NVP_AUX( \
         0 \
        ,oname \
        ,~ \
        ,__VA_ARGS__ \
    )

#define YAS_OBJECT_STRUCT_NVP(oname, sname, ...) \
    __YAS_OBJECT_NVP_AUX( \
         1 \
        ,oname \
        ,sname \
        ,__VA_ARGS__ \
    )

/***************************************************************************/

#define __YAS_DEFINE_STRUCT_SERIALIZE_AUX(nvp, oname, ...) \
    template<typename Archive> \
    void serialize(Archive &ar) const { \
        auto o = YAS_PP_IF(nvp, YAS_OBJECT_NVP, YAS_OBJECT) \
        ( \
             oname \
            ,__VA_ARGS__ \
        ); \
        ar & o; \
    } \
    template<typename Archive> \
    void serialize(Archive &ar) { \
        auto o = YAS_PP_IF(nvp, YAS_OBJECT_NVP, YAS_OBJECT) \
        ( \
             oname \
            ,__VA_ARGS__ \
        ); \
        ar & o; \
    }

#define YAS_DEFINE_STRUCT_SERIALIZE(oname, ...) \
    __YAS_DEFINE_STRUCT_SERIALIZE_AUX(0, oname, __VA_ARGS__)

#define YAS_DEFINE_STRUCT_SERIALIZE_NVP(oname, ...) \
    __YAS_DEFINE_STRUCT_SERIALIZE_AUX(1, oname, __VA_ARGS__)

/***************************************************************************/

#define __YAS_DEFINE_INTRUSIVE_SERIALIZE_AUX(nvp, oname, tname, ...) \
    template<typename Archive> \
    void serialize(Archive &ar, const tname &t) { \
        auto o = YAS_PP_IF(nvp, YAS_OBJECT_STRUCT_NVP, YAS_OBJECT_STRUCT) \
        ( \
             oname \
            ,t \
            ,__VA_ARGS__ \
        ); \
        ar & o; \
    } \
    template<typename Archive> \
    void serialize(Archive &ar, tname &t) { \
        auto o = YAS_PP_IF(nvp, YAS_OBJECT_STRUCT_NVP, YAS_OBJECT_STRUCT) \
        ( \
             oname \
            ,t \
            ,__VA_ARGS__ \
        ); \
        ar & o; \
    }

#define YAS_DEFINE_INTRUSIVE_SERIALIZE(oname, tname, ...) \
    __YAS_DEFINE_INTRUSIVE_SERIALIZE_AUX(0, oname, tname, __VA_ARGS__)

#define YAS_DEFINE_INTRUSIVE_SERIALIZE_NVP(oname, tname, ...) \
    __YAS_DEFINE_INTRUSIVE_SERIALIZE_AUX(1, oname, tname, __VA_ARGS__)

/***************************************************************************/

} // ns yas

#endif // __yas__object_hpp
